import { PublicKey } from '@solana/web3.js'
import { ApiV3PoolInfoItem, TickUtils, ApiV3PoolInfoConcentratedItem, TokenInfo } from '@pancakeswap/solana-core-sdk'
import Decimal from 'decimal.js'
import dayjs from 'dayjs'
import { PoolInfo } from '@pancakeswap/solana-clmm-sdk'
import { getPoolName } from '@/features/Pools/util'
import { wSolToSolString, getMintSymbol } from '@/utils/token'
import { toTotalPercent } from '@/utils/numberish/toPercentString'
import { trimTrailZero } from '@/utils/numberish/formatter'
import { useAppStore } from '@/store/useAppStore'
import { AprKey, TimeAprData, FormattedPoolInfoItem } from './type'

const calcFeeApr = (fee: number, tvl: number, period: 'day' | 'week' | 'month' = 'day') => {
  if (!fee || !tvl) {
    return 0
  }
  const feePerYear = new Decimal(fee).mul(period === 'day' ? 365 : period === 'week' ? new Decimal(365).div(7) : 12)
  return feePerYear.div(tvl).mul(100).toNumber()
}

export const formatAprData = (data: PoolInfo): ApiV3PoolInfoItem => {
  const dayFeeApr = data.day.feeApr ?? calcFeeApr(data.day.volumeFee, data.tvl, 'day')
  const weekFeeApr = data.week.feeApr ?? calcFeeApr(data.week.volumeFee, data.tvl, 'week')
  const monthFeeApr = data.week.feeApr ?? calcFeeApr(data.month.volumeFee, data.tvl, 'month')
  return {
    ...data,
    day: {
      ...data.day,
      rewardApr: data.day.rewardApr ?? [],
      apr: data.day.apr ?? dayFeeApr,
      feeApr: dayFeeApr
    },
    week: {
      ...data.week,
      rewardApr: data.week.rewardApr ?? [],
      apr: data.week.apr ?? weekFeeApr,
      feeApr: data.week.feeApr ?? weekFeeApr
    },
    month: {
      ...data.month,
      rewardApr: data.month.rewardApr ?? [],
      apr: data.month.apr ?? monthFeeApr,
      feeApr: data.month.feeApr ?? monthFeeApr
    },
    rewardDefaultPoolInfos: data.rewardDefaultPoolInfos ?? 'Clmm',
    rewardDefaultInfos: data.rewardDefaultInfos ?? [],
    pooltype: data.pooltype ?? []
  }
}

// if reward expired more than 10 days, do not display
const isRewardValid = (time?: number) => !time || Date.now() - time * 1000 < 864000000

export function formatPoolData(pool: ApiV3PoolInfoItem, tokenList?: TokenInfo[]): FormattedPoolInfoItem {
  const allApr: TimeAprData = Object.values(AprKey).reduce(
    (acc, cur) => {
      const aprData = pool[cur]
      return {
        ...acc,
        [cur]: [
          {
            apr: aprData.feeApr ?? 0,
            percent: toTotalPercent(aprData.feeApr ?? 0, aprData.apr ?? 0),
            isTradingFee: true
          },
          ...(aprData.rewardApr
            ?.filter((_, idx) => !!pool.rewardDefaultInfos[idx] && isRewardValid(pool.rewardDefaultInfos[idx].endTime))
            .map((r, idx) => ({
              apr: r,
              percent: toTotalPercent(r, aprData.apr ?? 0),
              token: { ...pool.rewardDefaultInfos[idx].mint }
            })) ?? [])
        ]
      }
    },
    {
      [AprKey.Day]: [],
      [AprKey.Week]: [],
      [AprKey.Month]: []
    }
  )

  const formattedRewardInfos = pool.rewardDefaultInfos
    .filter((r) => isRewardValid(r.endTime))
    .map((r) => {
      const apr = allApr[AprKey.Day].find((data) => data.token?.address === r.mint.address)?.apr || 0
      const now = dayjs(Date.now() + useAppStore.getState().chainTimeOffset)
      const openTime = r.startTime ? dayjs(r.startTime * 1000) : now
      const endTime = r.endTime ? dayjs(r.endTime * 1000) : now
      const totalRewards = new Decimal(r.perSecond || 0)
        .mul(endTime.diff(openTime, 'seconds'))
        .div(10 ** r.mint.decimals)
        .toString()
      const ongoing = openTime.isBefore(now, 'seconds') && endTime.isAfter(now, 'seconds')
      const ended = endTime.isBefore(now, 'seconds') || r.perSecond <= 0
      const unEmitRewards = new Decimal(Math.max(endTime.diff(now, 'seconds'), 0))
        .mul(r.perSecond || 0)
        .div(10 ** r.mint.decimals)
        .toString()

      return {
        ...r,
        apr,
        startTime: openTime.valueOf(),
        endTime: endTime.valueOf(),
        weekly: new Decimal(r.perSecond || 0)
          .mul(60 * 60 * 24 * 7)
          .div(10 ** r.mint.decimals)
          .toString(),
        daily: new Decimal(r.perSecond || 0)
          .mul(60 * 60 * 24)
          .div(10 ** r.mint.decimals)
          .toString(),
        periodString: `${openTime.format('YYYY/MM/DD')} - ${endTime.format('YYYY/MM/DD')}`,
        periodDays: endTime.diff(openTime, 'days'),
        unEmit: unEmitRewards,
        totalRewards,
        upcoming: r.startTime ? openTime.isBefore(now) : false,
        ongoing,
        ended,
        mint: {
          ...r.mint,
          symbol: getMintSymbol({ mint: r.mint, transformSol: true })
        }
      }
    })

  const poolDecimals = Math.max(pool.mintA.decimals, pool.mintB.decimals)
  const recommendDecimal = (val: number | string | Decimal) => {
    const valDecimal = new Decimal(val)
    if (valDecimal.gte(1000)) return 2
    if (valDecimal.gte(10)) return 4
    return poolDecimals
  }

  const mintAInTokenList = tokenList?.find((t) => t.address === pool.mintA.address)
  const mintBInTokenList = tokenList?.find((t) => t.address === pool.mintB.address)
  const { mintA } = pool
  // replace mint logoURI
  if (mintAInTokenList && mintA) {
    mintA.logoURI = mintAInTokenList.logoURI
  }
  if (mintA && mintA?.symbol === 'UNKNOWN' && mintAInTokenList) {
    mintA.symbol = mintAInTokenList?.symbol
    mintA.decimals = mintAInTokenList?.decimals
    mintA.name = mintAInTokenList?.name
  }

  const { mintB } = pool
  if (mintBInTokenList && mintB) {
    mintB.logoURI = mintBInTokenList.logoURI
  }
  if (mintB && mintB?.symbol === 'UNKNOWN' && mintBInTokenList) {
    mintB.symbol = mintBInTokenList?.symbol
    mintB.decimals = mintBInTokenList?.decimals
    mintB.name = mintBInTokenList?.name
  }

  const rewardMints = pool.rewardDefaultInfos.map((r) => r.mint)
  const rewardMintsInTokenList = tokenList?.filter((t) => rewardMints.some((r) => r.address === t.address))
  rewardMintsInTokenList?.forEach((t) => {
    const rewardMint = pool.rewardDefaultInfos.find((r) => r.mint.address === t.address)
    if (rewardMint) {
      rewardMint.mint.logoURI = t.logoURI
    }
  })

  const weeklyRewards = pool.rewardDefaultInfos
    .filter((r) => isRewardValid(r.endTime))
    .map((r) => {
      const amount = new Decimal(r.perSecond || 0).mul(60 * 60 * 24 * 7).div(10 ** r.mint.decimals)
      return {
        orgAmount: amount.toString(),
        amount: trimTrailZero(amount.toFixed(r.mint.decimals)) as string,
        token: r.mint,
        startTime: r.startTime,
        endTime: r.endTime
      }
    })

  return {
    ...formatAprData(pool),
    poolName: getPoolName(pool),
    poolDecimals,
    recommendDecimal,
    isOpenBook: pool.pooltype.includes('OpenBookMarket'),
    mintA: {
      ...pool.mintA,
      symbol: wSolToSolString(pool.mintA.symbol) || pool.mintA.address.substring(0, 6)
    },
    mintB: {
      ...pool.mintB,
      symbol: wSolToSolString(pool.mintB.symbol) || pool.mintB.address.substring(0, 6)
    },
    weeklyRewards,
    allApr,
    totalApr: {
      [AprKey.Day]: allApr[AprKey.Day].reduce((a, { apr: b }) => a + Number(b), 0),
      [AprKey.Week]: allApr[AprKey.Week].reduce((a, { apr: b }) => a + Number(b), 0),
      [AprKey.Month]: allApr[AprKey.Month].reduce((a, { apr: b }) => a + Number(b), 0)
    },
    formattedRewardInfos,
    isRewardEnded: !formattedRewardInfos.some((r) => !r.ended)
  }
}

export const poolInfoCache: Map<string, ApiV3PoolInfoItem> = new Map()

export const getTickArrayAddress = (props: { pool: ApiV3PoolInfoConcentratedItem; tickNumber: number }) =>
  TickUtils.getTickArrayAddressByTick(
    new PublicKey(props.pool.programId),
    new PublicKey(props.pool.id),
    props.tickNumber,
    props.pool.config.tickSpacing
  )
